package kernel.stl;

import kernel.stl.exception.InvalidZIndexException;
import kernel.stl.exception.UndefinedPointException;

/**
 *
 * @author George
 */
public class LineSegment {
    
    private PointXYZ point1;
    private PointXYZ point2;
    
    public LineSegment(PointXYZ p1, PointXYZ p2) {
        this.point1 = p1;
        this.point2 = p2;
    }
    
    public boolean isConnectedToPoint1(LineSegment otherLine, double tolerance) {
        if (this.getPoint1().equals(otherLine.getPoint1(), tolerance) ||
                this.getPoint1().equals(otherLine.getPoint2(), tolerance))
            return true;
        else
            return false;
    }
    
    public boolean isConnectedToPoint2(LineSegment otherLine, double tolerance) {
        if (this.getPoint2().equals(otherLine.getPoint1(), tolerance) ||
                this.getPoint2().equals(otherLine.getPoint2(), tolerance))
            return true;
        else
            return false;
    }
    
    /**Verifica se o segmento de reta está conectado ao outro segmento de reta, que é passado por parâmetro.
     * @param otherLine segmento de reta a ser analisado
     * @param tolerance erro máximo tolerado
     * @return true caso as retas estejam conectadas e false no caso contrário.
     */
    public boolean isConnectedTo(LineSegment otherLine, double tolerance) {
        if (this.isConnectedToPoint1(otherLine, tolerance) ||
                this.isConnectedToPoint2(otherLine, tolerance))
            return true;
        else
            return false;
    }
    
    public LineSegment newInvertedLine() {
        return new LineSegment(this.point2, this.point1);
    }
    
    public boolean isAPoint(double tolerance) {
        return this.point1.equals(this.point2, tolerance);
    }
    
    public String asString() {
        return "[" + this.point1.asString() + " " + this.point2.asString() + "] ";
    }
    
    public double getMaxX() {
        if (this.point1.getX() > this.getPoint2().getX())
            return this.point1.getX();
        else
            return this.point2.getX();
    }
    
    public double getMaxY() {
        if (this.point1.getY() > this.getPoint2().getY())
            return this.point1.getY();
        else
            return this.point2.getY();
    }
    public double getMaxZ() {
        if (this.point1.getZ() > this.getPoint2().getZ())
            return this.point1.getZ();
        else
            return this.point2.getZ();
    }

    public double getMinX() {
        if (this.point1.getX() < this.getPoint2().getX())
            return this.point1.getX();
        else
            return this.point2.getX();
    }
    
    public double getMinY() {
        if (this.point1.getY() < this.getPoint2().getY())
            return this.point1.getY();
        else
            return this.point2.getY();
    }
    public double getMinZ() {
        if (this.point1.getZ() < this.getPoint2().getZ())
            return this.point1.getZ();
        else
            return this.point2.getZ();
    }
    
    private PointXYZ getPointWithMaxZ() {
        if (this.point1.getZ() > this.getPoint2().getZ())
            return this.point1;
        else
            return this.point2;        
    }
    
    private PointXYZ getPointWithMinZ() {
        if (this.point1.getZ() <= this.getPoint2().getZ())
            return this.point1;
        else
            return this.point2;
    }
    
    /**Retorna o ponto da reta correspondente a um índice z.
     * @param zIndex índice z usado para localizar o ponto
     * @return o ponto correspondente ao índice z
     * @throws kernel.stl.exception.InvalidZIndexException caso seja passado um índice inválido.
     */
        public PointXYZ getPointAt(double zIndex) throws InvalidZIndexException, UndefinedPointException {     
        if (this.point1.getZ() == this.point2.getZ())
            throw new UndefinedPointException(); //não é possível determinar o
                                                 //ponto neste caso
        
        if ((this.getMaxZ() < zIndex) || (this.getMinZ() > zIndex))
            throw new InvalidZIndexException();
        
        double percentage = (zIndex - this.getMinZ()) / (this.getMaxZ() - this.getMinZ());
        
        PointXYZ pointWithMaxZ = getPointWithMaxZ();
        PointXYZ pointWithMinZ = getPointWithMinZ();
        
        double x;
        double y;
        
        if (percentage > 50) {
            double deltaX = this.getMaxX() - this.getMinX();
            double deltaY = this.getMaxY() - this.getMinY();
            
            if (pointWithMaxZ.getX() > pointWithMinZ.getX())
                x = this.getMaxX() - deltaX * percentage;
            else
                x = this.getMinX() + deltaX * percentage;
            
            if (pointWithMaxZ.getY() > pointWithMinZ.getY())
                y = this.getMaxY() - deltaY * percentage;
            else
                y = this.getMinY() + deltaY * percentage;
        }
        else {
            double deltaX = this.getMaxX() - this.getMinX();
            double deltaY = this.getMaxY() - this.getMinY();
            
            if (pointWithMaxZ.getX() > pointWithMinZ.getX())
                x = this.getMinX() + deltaX * percentage;
            else
                x = this.getMaxX() - deltaX * percentage;
            
            if (pointWithMaxZ.getY() > pointWithMinZ.getY())
                y = this.getMinY() + deltaY * percentage;
            else
                y = this.getMaxY() - deltaY * percentage;
        }
            
        return new PointXYZ(x, y, zIndex);
    }
    
    public PointXYZ getMidPoint() {
        double x = (this.point1.getX() + this.point2.getX()) / 2;
        double y = (this.point1.getY() + this.point2.getY()) / 2;
        double z = (this.point1.getZ() + this.point2.getZ()) / 2;
        
        return new PointXYZ(x, y, z);
    }
    
    public PointXYZ getPointWithMinX() {
        if (point1.getX() <= point2.getX())
            return this.point1;
        else
            return this.point2;
    }
    
    public PointXYZ getPointWithMaxX() {
        if (point1.getX() > point2.getX())
            return this.point1;
        else
            return this.point2;
    }
    
    public PointXYZ getPoint1() {
        return this.point1;
    }
    
    public PointXYZ getPoint2() {
        return this.point2;
    }
    
    public double smallerDistanceBetweenExtremities(LineSegment otherLineSegment) {
        double distance = this.getPoint1().distanceTo(otherLineSegment.getPoint1());
        double result = distance;
        
        distance = this.getPoint1().distanceTo(otherLineSegment.getPoint2());
        
        if (distance < result)
            result = distance;
        
        distance = this.getPoint2().distanceTo(otherLineSegment.getPoint1());
        
        if (distance < result)
            result = distance;
        
        distance = this.getPoint2().distanceTo(otherLineSegment.getPoint2());
        
        if (distance < result)
            result = distance;
        
        return result;
    }
    
    public boolean equals(LineSegment otherLineSegment, double tolerance) {
        return this.equals(otherLineSegment.getPoint1(), otherLineSegment.getPoint2(), tolerance);
    }
    
    public boolean equalsOnSameSide(LineSegment otherLineSegment, double tolerance) {
        if (!this.getPoint1().equals(otherLineSegment.getPoint1()))
            return false;
        else if (!this.getPoint2().equals(otherLineSegment.getPoint2()))
            return false;
        return true; 
    }
    
    public boolean equals(PointXYZ point1, PointXYZ point2, double tolerance) {
        if (this.point1.equals(point1) && this.point2.equals(point2))
            return true;
        else if (this.point1.equals(point2, tolerance) && this.point2.equals(point1, tolerance))
            return true;
        else
            return false;
    }

}
